package core.reports;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import core.CoreFacade;
import core.Product;
import core.ProductGroup;
import core.StorageUnit;
import core.ProductContainer;
import core.Amount;
import core.Unit;

/**
 * This class will create the N-Month Supply Report:
 * It will be responsible for getting the necessary data
 * through the visitor pattern, and then constructing the report
 * through the builder pattern.
 * 
 */
public class N_MonthSupplyGuide implements IDirectorVisitor
{
	/**
	 * This class is used to copare two containerProduct pair classes.
	 * @author Spencer
	 *
	 */
	private class MyObjComparable implements Comparator<Product>{
		 
	    @Override
	    public int compare(Product o1, Product o2) 
	    {
	    	return o1.getDescription().compareTo(o2.getDescription());
	    }
	}
	
	/** 
	 * Which type of output file will we print 
	 * 0 = PDF
	 * 1 = HTML
	 * 2 = DATA 
	 */
	private int outType = -1;
	
	/** how many months is the report for */
	private int numberOfMonths;
	
	/** This keeps a count of how many items are in each container */
	private Map<ProductContainer, Amount> containerToCount = null;
	
	/** This keeps a list of all the containers that need to be printed out */
	private List<ProductContainer> containerToPrint = null;
	
	/** This keeps a list of all the products that need to be printed out */
	private List<Product> productsToPrint = null;
	
	/** This keeps a list of all the products that are in or below a container */
	private Map<ProductContainer, ArrayList<ContainerProductPair>> containersToProducts;

	/**
	 * Default Constructor
	 */
	public N_MonthSupplyGuide(int numMonths, String format)
	{
		if (format == "PDF")
		{
			System.out.println("PDF TYPE");
			outType = 0;
		}
		else if (format == "HTML")
		{
			System.out.println("HTML TYPE");
			outType = 1;
		}
		else if (format == "DATA")
		{
			System.out.println("DATA TYPE");
			outType = 2;
		}
		
		numberOfMonths = numMonths;
		containerToCount = new HashMap<ProductContainer, Amount>();
		containerToPrint = new ArrayList<ProductContainer>();
		productsToPrint = new ArrayList<Product>();
		containersToProducts = new HashMap<ProductContainer, ArrayList<ContainerProductPair>>();
		checkProducts();
	}
	
	/**
	 * Checks which products needs to be printed. and adds them to 
	 * the productsToPRint object.
	 */
	private void checkProducts()
	{
		List<Product> products = CoreFacade.getInstance().getAllProductInRoot();
		
		for (int i = 0; i < products.size(); i++)
		{
			int tms = products.get(i).getThreeMonthSupply();
			
			if (tms == 0) continue;
			
			int numInSystem = CoreFacade.getInstance().getNumItemsInSystem(products.get(i));
			
			tms = (int) Math.ceil( (float)tms * ((float)numberOfMonths / 3.0f) );
			
			if (tms > numInSystem)
			{
				productsToPrint.add(products.get(i));
			}
		}
		
		//sort the products
		Collections.sort(products, new MyObjComparable());
	}
	
	@Override
	/**
	 * When creating the N-Month supply report this method
	 * will get the necessary information from a productgroup
	 * @param pg the productGroup
	 * @return true if it was successful
	 */
	public boolean visitProductGroup(ProductGroup pg, TraversalOrder order) 
	{
		boolean success = false;
		
		if (order == TraversalOrder.PostOrder)
		{
			success = visitProductGroupPost(pg);
		}
		else if (order == TraversalOrder.PreOrder)
		{
			success = visitProductGroupPre(pg);
		}
		else
		{
			//Should never get here
			success = false;
		}
		
		return success;
	}
	
	/**
	 * This method is called when the 'visit' method has the order value
	 * of Preorder.  This does all the calculations needed on a preorder
	 * visit of the node.  This means that it will compare the needed
	 * supply with the supply we currently have.  If needed > have then 
	 * the Product is added to a list of containers that don't have a 
	 * sufficent supply for the number of months.
	 * 
	 * @param pg -- The product Group that we are visiting.
	 * @return True
	 */
	private boolean visitProductGroupPre(ProductGroup pg)
	{
		//first off... check to see if the product group even has
		//a three month supply value defined.
		if (pg.getThreeMonthSupplyAmount().getSize() == 0) return true;
		Amount currentGroupTotal = new Amount(pg.getThreeMonthSupplyAmount().getUnit(), 0.0f);
		ArrayList<ContainerProductPair> products = containersToProducts.get(pg);
		for (int i = 0; i < products.size(); i++)
		{
			Product currProduct = products.get(i).product;
			if (pg.getThreeMonthSupplyAmount().isCompatableAmounts(currProduct.getAmount()))
				//isCompatableAmounts(pg.getThreeMonthSupplyAmount(), currProduct.getAmount()))
			{
				//they are the same unit type so add this products amount
				//to the currentGroupTotal amount
				for (int j = 0; j < CoreFacade.getInstance().getCountOfProdInProductContainer(
						currProduct, products.get(i).container); j++)
				{
					currentGroupTotal.add(currProduct.getAmount());
				}
			}
			else if (pg.getThreeMonthSupplyAmount().getUnit() == Unit.count)
			{
				currentGroupTotal.setSize( currentGroupTotal.getSize() + 
						CoreFacade.getInstance().getCountOfProdInProductContainer(currProduct, 
								products.get(i).container));
			}
		}
		//calculate the current supply needed
		//tms = (int) Math.ceil( (float)tms * ((float)numberOfMonths / 3.0f) );
		float supplyNeeded = (float)(float)pg.getThreeMonthSupplyAmount().getSize() * 
				((float)numberOfMonths/3.0f);
		if(supplyNeeded > currentGroupTotal.getSize())
		{
			//add this product container to the list
			containerToPrint.add(pg);
			containerToCount.put(pg, currentGroupTotal);
		}
		return true;
	}
	
	/**
	 * This method is called when a visit method has an order parameter
	 * of PostOrder.  This will calculate the amount of supply contained
	 * within a Product Group and within all of it's children producct
	 * groups.  It converts the amount of the product to the amount of
	 * the product group and adds it to the total.
	 * 
	 * @param pg
	 * @return
	 */
	private boolean visitProductGroupPost(ProductGroup pg)
	{
		ArrayList<ContainerProductPair> products = new ArrayList<ContainerProductPair>();
		
		Set<Product> p = CoreFacade.getInstance().getAllProducts(pg);
		Iterator it = p.iterator();
		
		while(it.hasNext())
		{
			Product curP = (Product) it.next();
			
			products.add(new ContainerProductPair(pg, curP));
		}
		
		List<ProductGroup> children = pg.getProductGroups();
		
		for (int i = 0; i < children.size(); i++)
		{
			products.addAll(containersToProducts.get(children.get(i)));
		}
		
		containersToProducts.put(pg, products);
		
		return true;
		/*Set<Product> products = CoreFacade.getInstance().getAllProducts(pg);
		Iterator<Product> it = products.iterator();
		
		Amount currentGroupTotal = new Amount(pg.getThreeMonthSupplyAmount().getUnit(), 0.0f);
		
		while (it.hasNext())
		{
			Product currProduct = it.next();
			
			if (pg.getThreeMonthSupplyAmount().isCompatableAmounts(currProduct.getAmount()))
				//isCompatableAmounts(pg.getThreeMonthSupplyAmount(), currProduct.getAmount()))
			{
				//they are the same unit type so add this products amount
				//to the currentGroupTotal amount
				for (int i = 0; 
						i < CoreFacade.getInstance().getCountOfProdInProductContainer(currProduct, 
								pg); i++)
				{
					currentGroupTotal.add(currProduct.getAmount());
				}
			}
		}*/
		
		//now we have this groups total.
		//iff it has any children add thier compatible totals
		/*List<ProductGroup> childrenList = pg.getProductGroups();
		for (int i = 0; i < childrenList.size(); i++)
		{
			Amount childAmount = containerToCount.get(childrenList.get(i));
			
			if ((childAmount != null) && 
				(pg.getThreeMonthSupplyAmount().isCompatableAmounts(childAmount)))
			{
				//they have a value that can be added
				currentGroupTotal.add(childAmount);
			}
		}
		
		//now we have the final total amount for this group
		//add it to the map
		containerToCount.put(pg, currentGroupTotal);
		
		return true;*/
	}
	
	@Override
	/**
	 * When creating the N-Month supply report this method
	 * will get the necessary information from a storage unit
	 * @param su the storage unit
	 * @return true if it was successful
	 */
	public boolean visitStorageUnit(StorageUnit su, TraversalOrder order) 
	{
		//we don't need to do anything
		return true;
	}
	
	@Override
	/**
	 * After getting the data from the visit methods, this will
	 * then instruct the appropriate builder how to create the 
	 * necessary report
	 * @return true if it was successful
	 */
	public String construct(String s) 
	{		
		IBuilder report = null;
		
		switch(outType)
		{
		case 1: //HTML
			report = new HTMLBuilder("reports/N-MonthSupply-" + new Date().getTime() + ".html");
			break;
		case 2: //Data
			break;
		case 0: // PDF
		default: //default will be PDF
			report = new PDFBuilder("reports/N-MonthSupply-" + new Date().getTime() + ".pdf");
		}
		
		report.addTitle(numberOfMonths + "-Month Supply Report");
		
		report.addBlankLine();
		
		report.addSubTitle("Products");
		
		report.addTable(4);
		report.addColumnHeader("Description");
		report.addColumnHeader("Barcode");
		report.addColumnHeader(numberOfMonths + "-Month Supply");
		report.addColumnHeader("Current Supply");
		for (int i = 0; i < productsToPrint.size(); i++)
		{
			Product currProd = productsToPrint.get(i);
			
			report.addCell(currProd.getDescription());
			report.addCell(currProd.getBarcodeString());
			//tms = (int) Math.ceil( (float)tms * ((float)numberOfMonths / 3.0f) );
			report.addCell((float)currProd.getThreeMonthSupply() * ((float)numberOfMonths/3.0f) 
					+ " count");
			report.addCell(CoreFacade.getInstance().getNumItemsInSystem(currProd) + " count");
		}
		report.closeCurrentTable();
		
		report.addBlankLine();
		
		report.addSubTitle("Product Groups");
		
		report.addTable(4);
		report.addColumnHeader("Product Group");
		report.addColumnHeader("Storage Unit");
		report.addColumnHeader(numberOfMonths + "-Month Supply");
		report.addColumnHeader("Current Supply");
		for (int i = 0; i < containerToPrint.size(); i++)
		{
			ProductGroup pg = (ProductGroup)containerToPrint.get(i);
			
			report.addCell(pg.getName());
			report.addCell(CoreFacade.getInstance().getParentStorageUnit(pg).getName());
			
			//tms = (int) Math.ceil( (float)tms * ((float)numberOfMonths / 3.0f) );
			
			Amount tmp3MonthSupply = new Amount(pg.getThreeMonthSupplyAmount().getUnit(),
				(float)pg.getThreeMonthSupplyAmount().getSize() * ((float)numberOfMonths / 3.0f));
					//pg.getThreeMonthSupplyAmount().getSize() * (numberOfMonths/3));
			
			report.addCell(tmp3MonthSupply.toString());
			report.addCell(containerToCount.get(pg).toString());
		}
		report.closeCurrentTable();
		
		/*report.addBlankLine();
		report.addBlankLine();
		report.addBlankLine();
		
		report.addTable(4);
		report.addColumnHeader("Header 1");
		report.addCell("Data1");
		report.addCell("Data2");
		report.addCell("Data3");
		report.addCell("Data4");
		report.addCell("Data5");
		report.closeCurrentTable();
		
		report.addBlankLine();
		report.addSubTitle("Table 1");
		report.addTable(4);
		report.addColumnHeader("Header 2");
		report.addCell("Data21");
		report.addCell("Data22");
		report.addCell("Data23");
		report.addCell("Data24");
		report.addCell("Data25");
		report.closeCurrentTable();
		
		report.addBlankLine();
		report.addBlankLine();
		
		report.addSubTitle("Table 2");
		
		report.addList();
		report.addListItem("List item 1");
		report.addListItem("List item 2");
		report.addListItem("List item 3");
		report.addListItem("List item 4");
		report.closeCurrentList();*/
		
		report.finish();
		
		switch(outType)
		{
		case 1: //HTML
			return ((HTMLBuilder)report).getFileName();
		case 2: //Data
			return "";
		case 0: // PDF
		default: //default will be PDF
			return ((PDFBuilder)report).getFileName();
		}
	}

}
